﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using Object = UnityEngine.Object;

namespace JH
{
    /// <summary>
    ///  运行时用来隔离注入逻辑的类
    /// </summary>
    public static class RuntimeInjector
    {
        /// <summary>
        ///  一般不会涉及多线程，但考虑极端情况下的多线程问题，使用锁来保护
        /// </summary>
        private static readonly object LockObject = new object();

        /// <summary>
        /// 进行依赖注入
        /// </summary>
        ///  <param name="services">所有可以用来注入的服务</param>
        ///  <param name="attributes">所有带有特性标记的实例和成员集合</param>
        public static void DoInject(ConcurrentDictionary<Type, List<WeakReference<object>>> services,
            ConcurrentDictionary<WeakReference<object>, List<MemberInfo>> attributes)
        {
            // 逐个处理每个标记的实例
            foreach (var entry in attributes)
            {
                // 获取弱引用
                var key = entry.Key;

                // 如果引用已经被回收，跳过
                if (!key.TryGetTarget(out var instance)) continue;

                // 单个实例 注入依赖
                InjectEachInstance(instance, entry.Value, services);
            }
        }

        /// <summary>
        ///  为实例注入依赖
        /// </summary>
        private static void InjectEachInstance(object instance, List<MemberInfo> members,
            ConcurrentDictionary<Type, List<WeakReference<object>>> services)
        {
            // 为每个标记的成员注入依赖
            foreach (MemberInfo member in members)
            {
                lock (LockObject)
                {
                    InjectEachMember(instance, member, services);
                }
            }
        }

        /// <summary>
        ///  为单个成员注入依赖
        /// </summary>
        /// <param name="instance">需要注入的实例</param>
        /// <param name="member"> 需要注入的成员</param>
        /// <param name="services">所有可以用来注入的服务</param>
        private static void InjectEachMember(object instance, MemberInfo member,
            ConcurrentDictionary<Type, List<WeakReference<object>>> services)
        {
            // 如果是字段
            if (member is FieldInfo fieldInfo) InjectField(instance, fieldInfo, services);

            // 如果是属性
            else if (member is PropertyInfo propertyInfo) InjectProperty(instance, propertyInfo, services);

            // 如果是方法
            else if (member is MethodInfo methodInfo) InjectMethod(instance, methodInfo, services);

            else Debug.LogError($"依赖注入失败！类{instance.GetType()}中存在，不支持的成员类型: {member.MemberType}", instance as Object);
        }

        /// <summary>
        ///  注入方法
        ///  </summary>
        ///  <param name="instance">需要注入的实例</param>
        ///  <param name="methodInfo">需要注入的方法</param>
        ///  <param name="services">所有可以用来注入的服务</param>
        private static void InjectMethod(object instance, MethodInfo methodInfo,
            ConcurrentDictionary<Type, List<WeakReference<object>>> services)
        {
            // 获取方法参数集合
            var parameters = methodInfo.GetParameters();

            // 获取特性
            var attribute = methodInfo.GetCustomAttribute<InjectAttribute>();
            if (attribute == null) throw new ArgumentNullException(nameof(attribute), "没有找到注入特性");

            //针对每一个参数，获取合适的注入实例
            var parametersList = new List<object>();
            for (var i = 0; i < parameters.Length; i++)
            {
                // 获取参数
                var parameter = parameters[i];

                // 获取合适的注入实例，这里构造一个假的特性信息
                var match = GetMatchInstance(instance, methodInfo.Name, parameter.ParameterType,
                    attribute.NamedList == null || attribute.NamedList.Count == 0 || i >= attribute.NamedList.Count
                        ? null
                        : new InjectAttribute(attribute.NamedList[i]),
                    services);

                //如果没找到合适的实例，打印错误
                if (match == null)
                {
                    Debug.LogError($"依赖注入失败，类{instance.GetType()}的方法{methodInfo.Name}" +
                                   $"没有找到合适参数{parameter.Name}的实例",
                        instance as Object);
                    return;
                }

                // 添加到参数列表
                parametersList.Add(match);
            }

            // 调用方法
            try
            {
                methodInfo.Invoke(instance, parametersList.ToArray());
            }
            catch (Exception e)
            {
                Debug.LogError($"依赖注入失败，类{instance.GetType()}的方法{methodInfo.Name}调用失败", instance as Object);
                Debug.LogException(e);
            }
        }

        /// <summary>
        ///  注入属性
        /// </summary>
        /// <param name="instance">需要注入的实例</param>
        /// <param name="propertyInfo">需要注入的属性</param>
        ///  <param name="services">所有可以用来注入的服务</param>
        private static void InjectProperty(object instance, PropertyInfo propertyInfo,
            ConcurrentDictionary<Type, List<WeakReference<object>>> services)
        {
            if (!propertyInfo.CanWrite)
            {
                Debug.LogError($"依赖注入警告：类 {instance.GetType()} 的属性 {propertyInfo.Name} 是只读的，无法注入实例。");
                return;
            }

            // 获取合适的注入实例
            var match = GetMatchInstance(instance, propertyInfo.Name, propertyInfo.PropertyType,
                propertyInfo.GetCustomAttribute<InjectAttribute>(),
                services);

            //如果没找到合适注入的实例，打印错误
            if (match == null)
            {
                Debug.LogError($"依赖注入失败，类{instance.GetType()}的属性{propertyInfo.Name}没有找到合适的实例", instance as Object);
                return;
            }

            // 注入实例
            propertyInfo.SetValue(instance, match);
        }

        /// <summary>
        ///  注入字段
        /// </summary>
        ///  <param name="instance">需要注入的实例</param>
        ///  <param name="fieldInfo">需要注入的字段</param>
        ///  <param name="services">所有可以用来注入的服务</param>
        private static void InjectField(object instance, FieldInfo fieldInfo,
            ConcurrentDictionary<Type, List<WeakReference<object>>> services)
        {
            // 获取合适的注入实例
            var match = GetMatchInstance(instance, fieldInfo.Name, fieldInfo.FieldType,
                fieldInfo.GetCustomAttribute<InjectAttribute>(),
                services);

            //如果没找到合适注入的实例，打印错误
            if (match == null)
            {
                Debug.LogError($"依赖注入失败，类{instance.GetType()}的字段{fieldInfo.Name}没有找到合适的实例", instance as Object);
                return;
            }

            // 注入实例
            fieldInfo.SetValue(instance, match);
        }

        /// <summary>
        ///  获取合适的注入实例
        /// </summary>
        /// <param name="instance">需要注入的实例</param>
        /// <param name="memberInfoName">标识符名称</param>
        /// <param name="type"> 需要注入的类型</param>
        /// <param name="attribute"> 注入特性</param>
        /// <param name="services">可用于注入的服务</param>
        private static object GetMatchInstance(object instance, string memberInfoName, Type type,
            InjectAttribute attribute,
            ConcurrentDictionary<Type, List<WeakReference<object>>> services)
        {
            // 获取可用于注入的实例集合
            var servicesInstances = GetServicesInstances(type, services);

            // 如果没有找到实例集合，打印错误
            if (servicesInstances == null || servicesInstances.Count == 0)
            {
                Debug.LogError($"在向实例 {instance} 的成员{memberInfoName}注入时，没有找到类型匹配的实例,类型为: {type.Name}",
                    instance as Object);
                return null;
            }

            // 获取合适注入的实例
            var match = GetMatchInstance(instance, memberInfoName, servicesInstances, attribute);
            return match;
        }

        /// <summary>
        ///  获取合适的注入实例
        /// </summary>
        private static object GetMatchInstance(object instance, string memberInfoName, List<object> servicesInstances,
            InjectAttribute injectAttribute)
        {
            // 如果只有一个
            if (servicesInstances.Count == 1) return servicesInstances[0];

            // 没找到注入特性或名称集合 或者名称集合为空
            if (injectAttribute?.NamedList is not { Count: > 0 })
            {
                Debug.LogWarning("在向实例 " + instance + " 的成员" + memberInfoName +
                                 $"注入时，发现了多个实例满足条件，但没有传入名称用来筛选，将使用第一个实例{servicesInstances[0]}进行注入", instance as Object);
                return servicesInstances[0];
            }

            //如果找到多个
            if (servicesInstances.Count > 1)
            {
                // 进行名称匹配,使用名称集合中的第一个名称进行匹配
                var nameMatch = servicesInstances.FindAll(i =>
                    i is Object obj && obj.name == injectAttribute.NamedList[0]);

                if (nameMatch.Count > 0)
                {
                    // 返回唯一实例
                    if (nameMatch.Count == 1) return nameMatch[0];

                    Debug.LogWarning("在向实例 " + instance + " 的成员" + memberInfoName +
                                     $"注入时，发现了多个实例满足名称为 {injectAttribute.NamedList[0]}的条件，将使用第一个实例{servicesInstances[0]}进行注入",
                        instance as Object);
                    return nameMatch[0];
                }
            }

            // 如果没有找到匹配的实例，返回空
            Debug.Log("在向实例 " + instance + " 的成员" + memberInfoName +
                      $"注入时，发现了多个实例满足条件，但没有找到匹配的名称，将使用第一个实例{servicesInstances[0]}进行注入", instance as Object);
            return null;
        }

        /// <summary>
        ///  传入类型，以及可以用来依赖注入的弱引用集合，获取可用的服务实例集合
        ///  total = 全部可用的实例集合
        ///  nameList = 名称接口实例集合
        /// </summary>
        private static List<object> GetServicesInstances(
            Type memberType,
            ConcurrentDictionary<Type, List<WeakReference<object>>> services)
        {
            // 获取类型对应的实例集合
            if (!services.TryGetValue(memberType, out var weakRefs))
                return null;

            // 筛选出可用的服务实例
            var servicesInstances = weakRefs.Select(x
                    => x.TryGetTarget(out var target) ? target : null)
                .Where(x => x != null).ToList();

            return servicesInstances;
        }
    }
}